package com.ea.eadp.nexus.test.data;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Map;

import org.testng.Assert;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;


public class PINEventValidatorFactory {
    public enum EventType {
        LOGIN("login"),
        REGISTRATION("registration"),
        ;
        
        String eventTypeName = null;
        
        private EventType(String eventTypeName) {
            this.eventTypeName = eventTypeName;
        }
        
        public String getEventTypeName() {
            return eventTypeName;
        }
    }
    
    public enum CoreField {
        TITLE_ID_TYPE("Title ID type", "tidt"),
        TITLE_ID("Title ID", "tid"),
        RELEASE_TYPE("Release type", "rel"),
        CLIENT_POST_TIMESTAMP("Client post timestamp", "ts_post"),
        SESSION_ID("Session ID", "sid"),
        PLATFORM("Platform", "plat"),
        EVENT_TYPE("Event type", "et"),
        STEP_NUMBER("Step number", "s"),
        PLAYER_ID_TYPE("Player ID type", "pidt"),
        PLAYER_ID("Player ID", "pid"),
        CLIENT_GENERATING_TIMESTAMP("Client generating timestamp", "ts_event"),
        EVENT_NAME("Event Name", "en"),
        CLIENT_IP_ADDRESS("Client IP address", "clientIP"),
        DOB("Player DOB", "dob"),
        ;
        
        String fullName = null;
        String propertyName = null;
        
        private CoreField(String fullName, String propertyName) {
            this.fullName = fullName;
            this.propertyName = propertyName;
        }
        
        public String getFullName() {
            return fullName;
        }
        
        public String getPropertyName() {
            return propertyName;
        }
    }
    
    public static PINEventValidator getValidator(EventType eventType) throws Exception {
        switch (eventType) {
            case LOGIN:
                return new LoginEventValidator();
            case REGISTRATION:
                return new RegistrationEventValidator();
            default:
                Assert.fail("unknown event type: " + eventType);
                return null; // not reachable
        }
    }
    
    public static interface PINEventValidator {
        void validateCoreFields(Map<String,String> event, Map<String,String> criteria) throws Exception;
        void validateAdditionalFields(Map<String,String> event, Map<String,String> criteria) throws Exception;
        void validateHeaders(Map<String,String> event, Map<String,String> criteria) throws Exception;
        void validateDeviceIDMap(Map<String,String> event, Map<String,String> criteria) throws Exception;
    }
    
    static abstract class AbstractPINEventValidator implements PINEventValidator {
        @Override
        public void validateCoreFields(Map<String,String> event, Map<String,String> criteria) throws Exception {
            for (CoreField coreField : CoreField.values()) {
                Assert.assertTrue(event.containsKey(coreField.getFullName()), coreField.getFullName() + " is absent");
                
                String coreFieldValue = event.get(coreField.getFullName());
                Assert.assertTrue(null != coreFieldValue, coreField.getFullName() + " is absent");
                
                DateFormat timestampFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.0");
                
                switch (coreField) {
                    case CLIENT_GENERATING_TIMESTAMP:
                    case CLIENT_POST_TIMESTAMP:
                        timestampFormat.parse(coreFieldValue);
                        break;
                    case EVENT_NAME:
                        Assert.assertEquals(coreFieldValue, getEventType().getEventTypeName(), coreField.getFullName() + "is wrong");
                        break;
                    case EVENT_TYPE:
                        Assert.assertEquals(coreFieldValue, "server_nucleus", coreField.getFullName() + "is wrong");
                        break;
                    case PLAYER_ID:
                        Long.parseLong(coreFieldValue);
                        break;
                    case PLAYER_ID_TYPE:
                        Assert.assertEquals(coreFieldValue, "nucleus", coreField.getFullName() + "is wrong");
                        break;
                    case RELEASE_TYPE:
                        Assert.assertEquals(coreFieldValue, "prod", coreField.getFullName() + "is wrong");
                        break;
                    case STEP_NUMBER:
                        Assert.assertEquals(coreFieldValue, "1", coreField.getFullName() + "is wrong");
                        break;
                    case TITLE_ID_TYPE:
                        Assert.assertEquals(coreFieldValue, "clientid", coreField.getFullName() + "is wrong");
                        break;
                    case CLIENT_IP_ADDRESS:
                    case PLATFORM:
                    case SESSION_ID:
                    case TITLE_ID:
                        Assert.assertTrue(coreFieldValue.length() > 0, coreField.getFullName() + " is empty unexpectedly");
                        break;
                }
            }

            reconcile(event, criteria);
        }
        
        @Override
        public void validateAdditionalFields(Map<String, String> event, Map<String, String> criteria) throws Exception {
            reconcile(getMappingField(event, "Event additional parameters"), criteria);
        }

        @Override
        public void validateHeaders(Map<String, String> event, Map<String, String> criteria) throws Exception {
            reconcile(getMappingField(event, "HTTP header"), criteria);
        }

        @Override
        public void validateDeviceIDMap(Map<String, String> event, Map<String, String> criteria) throws Exception {
            reconcile(getMappingField(event, "Device ID Map"), criteria);
        }
        
        protected void reconcile(Map<String,String> actual, Map<String,String> expected) throws Exception {
            if (null != expected) {
                for (Map.Entry<String,String> entry : expected.entrySet()) {
                    if (null == entry.getValue()) {
                        Assert.assertTrue(actual.containsKey(entry.getKey()), entry.getKey() + " is absent");
                    } else {
                        Assert.assertEquals(actual.get(entry.getKey()), entry.getValue(), entry.getKey() + " doesn't match");
                    }
                }
            }
        }
        
        protected Map<String,String> getMappingField(Map<String,String> event, String fieldName) throws Exception {
            if (null == event.get(fieldName)) {
                return null;
            } else {
                ObjectMapper mapper = new ObjectMapper();
                return mapper.readValue(event.get(fieldName), new TypeReference<Map<String,String>>(){});
            }
        }

        public abstract EventType getEventType();
    }
    
    static class LoginEventValidator extends AbstractPINEventValidator {
        @Override
        public EventType getEventType() {
            return EventType.LOGIN;
        }
    }

    static class RegistrationEventValidator extends AbstractPINEventValidator {
        @Override
        public EventType getEventType() {
            return EventType.REGISTRATION;
        }
        
        @Override
        public void validateCoreFields(Map<String,String> event, Map<String,String> criteria) throws Exception {
            super.validateCoreFields(event, criteria);
            
            DateFormat dobFormat = new SimpleDateFormat("yyyy-MM");
            dobFormat.parse(event.get(CoreField.DOB.getFullName()));
        }
    }
}
